#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <iostream>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <iomanip>
#include <algorithm>
#include <cstring>
#include <pthread.h>
#include <map>

using namespace std;

int sendSeq = 1;
int seq;
int code;
string message;
int size;

map<int, string> notAckedMessages;

string intToHex(int i) {
	stringstream stream;
	stream << setfill('0') << setw(sizeof(int) * 2) << hex << i;
	return stream.str();
}

string addPadding(int i, int size) {
		stringstream stream;
		stream << setfill('0') << setw(size) << i;
		return stream.str();
}

int hexToInt(string i) {

	int out;

	stringstream stream;

	stream << hex << i;
	stream >> out;

//	cout << out << flush;

	return out;
}

//resolvne messageCode na zpravu a zobrazi ji userovi
void printResultFromCode(int c) {
	string msg;
	if (c == 1) {
		msg = "Login OK.";
	}else if (c == 2) {
		msg = "Login failed.";
	}else if (c == 3){
		msg = "Message sent successfully.";
	}else if (c == 4) {
		msg = "Message stored for future delivery.";
	}else if (c == 5) {
		msg = "Recipient not found. Message discarded.";
	}else if (c == 6) {
		msg = "User is already logged in! Get OUT!";
	}else if (c == 7) {
		msg = "No new messages on server.";
	}else if (c == 8) {
		msg = "Receiving messages.";
	}

	cout << msg << endl << flush;

}

//prijme zpravy ulozene na serveru pro tohoto uzivatel a vypise je
void receiveNewMessages(FILE *fp) {
	int messageCode;
	int i = 1;

	cout << endl << flush;
	cout << "Message " << i++ << ":" << endl << flush;
	cout << message << endl << flush;
	cout << endl << flush;
	cout << endl << flush;


	while(messageCode != 7) {
		char sizeBuffer[9];
		fgets(sizeBuffer, sizeof(sizeBuffer), fp);
		int size = hexToInt(sizeBuffer);

		//nacteme kod zpravy - 01, 02, 03...
		char codeBuffer[3];
		fgets(codeBuffer, sizeof(codeBuffer), fp);
		int messageCode = atoi(codeBuffer);

		//no new messages
		if (messageCode == 7){
			printResultFromCode(7);
			return;
		}
		char messageBuffer[(size - 2) + 1];
		fgets(messageBuffer, sizeof(messageBuffer), fp);
		string msg = messageBuffer;

		cout << endl << flush;
		cout << "Message " << i++ << ":" << endl << flush;
		cout << msg << endl << flush;
		cout << endl << flush;
		cout << endl << flush;


	}

}

void receiveMessage() {
	printResultFromCode(code);

	message = "";
	code = 0;
	size = 0;

}

void sendMessage(int code, string msg, FILE *fp) {
	stringstream message;

	int actualSeqNo = sendSeq;
	message << addPadding(sendSeq++,8) << addPadding(code, 2) << msg;

	string msg1 = message.str();
	string length = intToHex(msg1.size());

	string finalMessage = length + msg1;

	cout << "Sending message: " << finalMessage << endl << flush;
	notAckedMessages[actualSeqNo] = finalMessage;


//	fflush(fp);
	fputs(finalMessage.c_str(), fp);
//	if (code != 0)
//		cout << "Sent......................................" << endl << flush;
	fflush (fp);
	cout << "Message sent" << endl << flush;
}

void *actualMessages(void *arg) {
	FILE *fp = (FILE*) arg;
	fd_set readset;
	FD_ZERO(&readset);
	FD_SET(fileno(fp),&readset);
	size = 0;

	while(true){
		cout << "waiting..." << endl << flush;
		int ret = select(fileno(fp)+1,&readset,NULL,NULL,NULL);
		if (FD_ISSET(fileno(fp), &readset)) {
				//ok cteme
			}else {
				cout << "CCCC" << endl << flush;
				continue;
			}
		char sizeBuffer[9];
		fgets(sizeBuffer, sizeof(sizeBuffer), fp);

		size = hexToInt(sizeBuffer);
//		if (size < 0)continue;

		//nacteme seqNo
		char seqNoBuffer[9];
		fgets(seqNoBuffer, sizeof(seqNoBuffer), fp);
		seq = hexToInt(seqNoBuffer);

		cout << "Received SeqNo: " << seq << endl << flush;

		//nacteme kod zpravy - 01, 02, 03...
		char codeBuffer[3];
		fgets(codeBuffer, sizeof(codeBuffer), fp);

		int messageCode = atoi(codeBuffer);
		code = messageCode;

		char messageBuffer[(size - 2 - 8) + 1];

		cout << "Pred bufferem" << endl << flush;
		fgets(messageBuffer, sizeof(messageBuffer), fp);
		cout << "Za bufferem" << endl << flush;
		message = messageBuffer;

		cout << "Code: " << code << endl << flush;

		if (code == 9) {
			//yeaaa, message have just arrived!
			cout << "-------------------------------" << endl << flush;
			cout << endl << flush;
			cout << "NEW MESSAGE" << endl << flush;
			cout << message << endl << flush;
			cout << endl << flush;
			cout << "-------------------------------" << endl << flush;
		}else if (code == 8) {
			receiveNewMessages(fp);
		}else if (code == 6) {
			fclose(fp);
			cout << message << endl << flush;
			exit(2);
		}else
			printResultFromCode(code);

		memset(sizeBuffer,NULL,sizeof(sizeBuffer));
		memset(codeBuffer,NULL,sizeof(codeBuffer));
		memset(seqNoBuffer,NULL,sizeof(seqNoBuffer));
		memset(messageBuffer,NULL,sizeof(messageBuffer));
//		fclose(fp);
	}
}
int main(int argc, char * argv[]) {
	char * server = argv[1];
	string port = argv[2];
	int port_number = atoi(port.c_str());
	string name = argv[3];

	hostent * record = gethostbyname(server);
	if (record == NULL) {
		herror("gethostbyname failed");
		exit(1);
	}
	in_addr * addressptr = (in_addr *) record->h_addr;

	int main_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (main_socket < 0) {
		perror("socket creation");
		exit(1);
	}

	sockaddr_in server_info;
	server_info.sin_family = AF_INET;
	server_info.sin_addr = *addressptr;
	server_info.sin_port = htons(port_number);

	int r = connect(main_socket, (sockaddr *) &server_info,
			sizeof(server_info));
	if (r < 0) {
		perror("connect");
		exit(1);
	}
	printf("Connected to %s\n", inet_ntoa(*addressptr));

	FILE * fp = fdopen(main_socket, "r+");

	pthread_t thread;
//	pthread_t thread1;

	pthread_create(&thread, 0, actualMessages, fp);
	pthread_detach(thread);

	sendMessage(1,name,fp);
	while (true) {

//		cout << "fp: " << fp << endl << flush;
		sleep(1);
		cout << "1 - get saved messages" << endl;
		cout << "2 - send message" << endl;
		cout << "99 - logout" << endl;

		string in1;
		getline(cin, in1);

		int in = atoi(in1.c_str());

		if (in == 1) {
			//get saved messages
			sendMessage(3, "", fp);
//			receiveMessage();
		}else if (in == 99) {
			//logout
			sendMessage(2, "",fp);
			break;
			exit(0);
		}else if (in == 2) {
			//send message
			cout << "Please insert your message in format 'username:message'" << endl << flush;
			string msg1;
			getline(cin, msg1);

			sendMessage(4, msg1, fp);
			receiveMessage();

		}
		fflush(fp);


	}

	fclose(fp);
	fclose (fp);
	close(main_socket);
}
